# File: hunger_games_entry.py
# Author: Josh Hieronymus

import random

class Player(object):
    """A Player is a contestant in brilliant.org's Hunger Games contest.
        
    A Player needs to decide, each round, for each other contestant,
    whether to hunt (cooperate) or slack (defect). A Player has a primary
    goal of being the last remaining player with food or having the most food
    when the game ends, and a secondary goal of merely remaining alive when the
    game ends.
    
    Generally, a Player will hunt when paired with hunters and slack
    when paired with slackers. However, this strategy would get in the way of
    the primary goal, so the Player will in a given round, with probability
    ALPHA, slack when paired with some hunters and hunt when paired with some
    slackers. The assumption is that since the Player's reputation changes the
    same amount for a round regardless of whether this action is performed, so
    it can't be tracked, and the action will cause the Player's toughest
    competitors, frequent hunters, to have less food than the Player at the end
    of the game.
    
    Constants:
        ALPHA: (float) the probability with which that the Player will slack
            against those with high reputation and cooperate with those with
            low reputation

    """
    ALPHA = .05
    
    def __init__(self):
        pass

    def hunt_choices(
            self, round_number, current_food, current_reputation, m,
            player_reputations):
        """Determine, for each other player in the current round, whether to hunt or slack.
        
        Args:
            round_number: (integer) the current round number
            current_food: (integer) the amount of food the player has at the
                beginning of the round
            current_reputation: (float) the player's reputation at the beginning
                of the round
            m: (integer) a number randomly chosen between 0 and P(P - 1) -- if at
                at least m hunts take place, every remaining player gets 2(P - 1)
                food
            player_reputations: (list of floats) the reputations of all the
                remaining players in the game
                
        Returns:
            hunt_decisions: (list of strings) a list containing the strings 'h'
                or 's' (for hunt or slack) for each other player
            
        """
        hunt_decisions = []
        
        # if there's only one other player, always slack
        if (len(player_reputations) == 1):
            hunt_decisions = all_slack(player_reputations)
            return hunt_decisions
        
        # if it's the first round, hunt with all players
        if (round_number == 1):
            hunt_decisions = all_hunt(player_reputations)
            return hunt_decisions
        
        # with probability (1 - ALPHA), hunt with all players with a reputation
        # as high or higher than the Player, slack with all others
        if (random.random() < (1 - Player.ALPHA)):
            for pr in player_reputations:
                if pr < current_reputation:
                    hunt_decisions.append('s')
                else:
                    hunt_decisions.append('h')
        # with probability ALPHA, slack the same number of times as above, but
        # slack instead with the players with the highest reputation
        else:
            # count how many players have a lower reputation than the Player--that
            # will be how many times the Player slacks this round
            times_to_slack = sum(1 for pr in player_reputations if pr < current_reputation)
            # determine the lowest reputation against which to slack this round
            descending_player_reputations = sorted(player_reputations, reverse=True)
            slack_baseline = descending_player_reputations[times_to_slack - 1]
            # determine how many players with the baseline reputation the Player
            # will slack against -- there may be multiple players with the same
            # baseline reputation
            player_reps_to_slack_against = descending_player_reputations[:times_to_slack]
            players_with_slack_baseline_to_slack_against = sum(
                    1 for pr in player_reps_to_slack_against if pr == slack_baseline)
            players_with_slack_baseline_slacked_against = 0
            for pr in player_reputations:
                if pr > slack_baseline:
                    hunt_decisions.append('s')
                elif ((pr == slack_baseline) and
                       (players_with_slack_baseline_slacked_against <
                        players_with_slack_baseline_to_slack_against)):
                    players_with_slack_baseline_slacked_against += 1
                    hunt_decisions.append('s')
                else:
                    hunt_decisions.append('h')
        return hunt_decisions

    def hunt_outcomes(self, food_earnings):
        """Take action based upon food earnings for the round.
        
        The current implementation doesn't do anything.
        
        Args:
            food_earnings: (list of integers) the amount of food earned from the
                last round's hunts
        
        Returns:
            nothing
        
        Side Effects:
            nothing
        """
        pass

    def round_end(self, award, m, number_hunters):
        """Take action based upon end-of-round information.
        
        The current implementation doesn't do anything.
        
        Args:
            award: (integer) the amount of bonus food earned from players'
                cooperation during the round
            m: (integer) a number randomly chosen between 0 and P(P - 1) -- if at
                at least m hunts take place, every remaining player gets 2(P - 1)
                food
            number_hunters: (integer) number of times players chose to hunt in
                the last round
        
        Returns:
            nothing
        
        Side Effects:
            nothing
        """
        pass

def all_hunt(player_reputations):
    """Hunt for an entire round.
    
    Args:
        player_reputations: (list of floats) the reputations of all the
            remaining players in the game
    
    Returns:
        hunt_decisions: (list of strings) a list containing 'h' for each
            remaining player
    """
    hunt_decisions = ['h' for x in player_reputations]
    return hunt_decisions

def all_slack(player_reputations):
    """Slack for an entire round.

    Args:
        player_reputations: (list of floats) the reputations of all the
            remaining players in the game
    
    Returns:
        hunt_decisions: (list of strings) a list containing 's' for each
            remaining player
    """
    hunt_decisions = ['s' for x in player_reputations]
    return hunt_decisions
